/*
 * Copyright (C) 2010 M. Hautle.
 * 
 * This file is part of SimpleEL.
 * 
 * SimpleEL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * SimpleEL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with SimpleEL. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.simpleel.util;

import java.util.*;

/**
 * Helper to merge lists.<br>
 * You may use this helper to update domain object list with lists of DTOs.<br>
 * Have a look at {@link CrossElementComparator}, it could be useful for the merging.
 * 
 * @param <T> The content type of the list
 * @author M. Hautle
 */
public class ListMergeHelper<T> {
    /** The merge list. */
    private List<T> merge = new ArrayList<T>();

    /** The comparator used to access {@link #merge}. */
    private final Comparator<T> comparator;

    /**
     * Default constructor.
     * 
     * @param comparator The comparator to use
     */
    public ListMergeHelper(Comparator<T> comparator) {
        this.comparator = comparator;
    }

    /**
     * Adds the given elements to the list.
     * 
     * @param elements The elements
     * @return The helper itself
     */
    public ListMergeHelper<T> addElements(T... elements) {
        for (T e : elements)
            addElement(e);
        return this;
    }

    /**
     * Adds the given elements to the list.
     * 
     * @param elements The elements
     * @return The helper itself
     */
    public ListMergeHelper<T> addElements(Collection<T> elements) {
        for (T e : elements)
            addElement(e);
        return this;
    }

    /**
     * Adds the given element in the list.
     * 
     * @param e The element to add
     */
    public void addElement(T e) {
        int index = Collections.binarySearch(merge, e, comparator);
        if (index < 0)
            index = -index - 1;
        merge.add(index, e);
    }

    /**
     * Returns the comparator used inside this helper.
     * 
     * @return The comparator.
     */
    public Comparator<T> getComparator() {
        return comparator;
    }

    /**
     * Returns the size of the list.
     * 
     * @return The size
     */
    public int getSize() {
        return merge.size();
    }

    /**
     * Appends all elements of the merge list to the passed list.
     * 
     * @param <C> The collection type
     * @param list The list
     * @return The passed list
     */
    public <C extends Collection<T>> C appendAllMerged(C list) {
        list.addAll(merge);
        return list;
    }

    /**
     * Retains all elements of the passed list if they have a corresponding element in this merge list.
     * 
     * @param <C> The content type
     * @param list The list
     * @return The removed elements of the passed list
     */
    @SuppressWarnings("unchecked")
    public <C> List<C> retainAllMerged(Collection<C> list) {
        final List<C> removed = new ArrayList<C>();
        for (Iterator<C> i = list.iterator(); i.hasNext();) {
            final C el = i.next();
            if (Collections.binarySearch(merge, (T) el, comparator) < 0) {
                removed.add(el);
                i.remove();
            }
        }
        return removed;
    }

    /**
     * Remove all elements of the passed list having a corresponding element in this merge list.
     * 
     * @param <C> The content type
     * @param list The list
     * @return The removed elements of the passed list
     */
    @SuppressWarnings("unchecked")
    public <C> List<C> removeAllMerged(Collection<C> list) {
        final List<C> removed = new ArrayList<C>();
        for (Iterator<C> i = list.iterator(); i.hasNext();) {
            final C el = i.next();
            if (Collections.binarySearch(merge, (T) el, comparator) > -1) {
                removed.add(el);
                i.remove();
            }
        }
        return removed;
    }

    /**
     * Returns the entry for the given keys.<br>
     * The used comparator must be able to compare the a key array against the list content!
     * 
     * @param keys The key values of the entry
     * @return The corresponding entry or null
     */
    public T getElementByKeys(Object... keys) {
        return getElement(keys);
    }

    /**
     * Returns the entry for the key values represented by the given object.<br>
     * The used comparator must be able to compare the passed argument against the list content!
     * 
     * @param o A object representing the key values
     * @return The corresponding entry or null
     */
    @SuppressWarnings("unchecked")
    public T getElement(Object o) {
        final int index = Collections.binarySearch(merge, (T) o, comparator);
        return index > -1 ? merge.get(index) : null;
    }

    /**
     * Removes the entry for the key values represented by the given object.<br>
     * The passed comparator must be able to compare the passed argument against the list content!
     * 
     * @param o A object representing the key values
     * @return The removed entry or null
     */
    @SuppressWarnings("unchecked")
    public T removeElement(Object o) {
        final int index = Collections.binarySearch(merge, (T) o, comparator);
        return index > -1 ? merge.remove(index) : null;
    }

    /**
     * Removes the elements of the given collection.<br>
     * The passed comparator must be able to compare the passed elements against the list content!
     * 
     * @param <C> The element type
     * @param elements The elements to remove
     * @return The removed elements
     */
    public <C> List<T> removeAll(Collection<C> elements) {
        final List<T> rem = new ArrayList<T>(elements.size());
        for (C e : elements) {
            final T r = removeElement(e);
            if (r != null)
                rem.add(r);
        }
        return rem;
    }

    /**
     * Removes the elements of the given collection.<br>
     * The passed comparator must be able to compare the passed elements against the list content!
     * 
     * @param <C> The element type
     * @param elements The elements to remove
     * @return The removed elements
     */
    @SuppressWarnings("unchecked")
    public <C> List<T> retainAll(Collection<C> elements) {
        final List<T> removed = new ArrayList<T>();
        final int[] retain = new int[elements.size()];
        int cnt = 0;
        // fetch the indices of the elements to retain
        for (C e : elements)
            retain[cnt++] = Collections.binarySearch(merge, (T) e, comparator);
        // order the indices to get the highest one at the end
        Arrays.sort(retain);
        // remove the elements in the 'reverse' order (highest first)
        int i = merge.size() - 1;
        for (int j = retain.length - 1; i > -1 && j > -1 && retain[j] > -1; i--) {
            // is the current index is higher than the index of the next element to retain?
            if (i > retain[j])
                removed.add(merge.remove(i));
            else
                j--;
        }
        // remove all other entries, so the ones having an lower index as the first (existing i.e. not <0) entry in the ret list
        for (; i > -1; i--)
            removed.add(merge.remove(i));
        return removed;
    }

    /**
     * Invokes the passed handler for the elements in the passed list and in this merge list.<br>
     * Do not modify those lists inside the passed handler!
     * 
     * @param <S> The content type of the list
     * @param list The list to merge into this one (do not modify it during the merge process!)
     * @param handler The handler to use
     */
    @SuppressWarnings("unchecked")
    public <S> void merge(Collection<S> list, IMergeHandler<T, S> handler) {
        final int[] retain = new int[list.size()];
        int cnt = 0;
        // handle the merged and the added elements
        for (S e : list) {
            final int index = retain[cnt++] = Collections.binarySearch(merge, (T) e, comparator);
            if (index < 0)
                handler.added(e);
            else
                handler.merged(e, merge.get(index));
        }
        // handle the removed elements
        Arrays.sort(retain);
        for (int i = 0, size = merge.size(); i < size; i++) {
            if (Arrays.binarySearch(retain, i) < 0)
                handler.removed(merge.get(i));
        }
    }

    /**
     * Handler for the merging process ({@link ListMergeHelper#merge(Collection, IMergeHandler)}).
     * 
     * @param <T> The content type of the merge list
     * @param <S> The content type of the source list
     * @author M. Hautle
     */
    public interface IMergeHandler<T, S> {
        /**
         * Called if a element of the source list does not exists in the merge list.
         * 
         * @param el The element of the source list
         */
        void added(S el);

        /**
         * Called if a element of the merge list does not exists in the source list.
         * 
         * @param el The element of the merge list
         */
        void removed(T el);

        /**
         * Called if a element of the source list has a corresponding element in the merge list.
         * 
         * @param src The element of the source list
         * @param dst The element of the merge list
         */
        void merged(S src, T dst);
    }
}
